#include "TinyJson.h"

namespace tinyUtil {
    inline bool isDigit(char ch) {
        return ch >= '0' && ch <= '9';
    }

    inline bool isWhiteSpace(char ch) {
        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
    }


    JsonValueTypeEnum getJsonTypeEnumByChar(char ch) {
        if (ch == 'n') return JsonValueTypeEnum::TYPE_NULL;
        if (ch == 't' || ch == 'f') return JsonValueTypeEnum::TYPE_BOOL;
        if (ch == '\"') return JsonValueTypeEnum::TYPE_STRING;
        if (ch == '-' || isDigit(ch)) return JsonValueTypeEnum::TYPE_NUMBER;
        if (ch == '[') return JsonValueTypeEnum::TYPE_ARRAY;
        if (ch == '{') return JsonValueTypeEnum::TYPE_OBJECT;
        throw std::runtime_error{ "no such json type" };
    }

    std::string jsonValueTypeStr(JsonValueTypeEnum jsonType) {
        switch (jsonType) {
            case JsonValueTypeEnum::TYPE_NULL: return "null";
            case JsonValueTypeEnum::TYPE_BOOL: return "bool";
            case JsonValueTypeEnum::TYPE_NUMBER: return "number";
            case JsonValueTypeEnum::TYPE_STRING: return "string";
            case JsonValueTypeEnum::TYPE_ARRAY: return "array";
            case JsonValueTypeEnum::TYPE_OBJECT: return "json object";
        }
        throw std::runtime_error{ "no such json type" };
    }

    void JsonValueNode::check(JsonValueTypeEnum type) const {
        if (type_ != type)
            throw std::runtime_error{ "This value type is "
                                      + jsonValueTypeStr(type_)
                                      + ", not "
                                      + jsonValueTypeStr(type)
            };
    }

    void JsonObjectFactory::skipWhiteSpace() {
        for (; isWhiteSpace(jsonStr_[pos_]); ++pos_);
    }

    std::string_view JsonObjectFactory::generateKey() {
        if (jsonStr_[pos_++] != '\"') throw std::runtime_error{ "json string key must start with \""};
        size_t begin = pos_;
        for (; jsonStr_[pos_] != '\"'; ++pos_);
        return jsonStr_.substr(begin, pos_++ - begin);
    }

    JsonValueType JsonObjectFactory::generateValue(JsonValueTypeEnum type) {
        switch (type) {
            case JsonValueTypeEnum::TYPE_NULL: return generateNullValue();
            case JsonValueTypeEnum::TYPE_BOOL: return generateBoolValue();
            case JsonValueTypeEnum::TYPE_STRING: return generateStrValue();
            case JsonValueTypeEnum::TYPE_NUMBER: return generateNumValue();
            case JsonValueTypeEnum::TYPE_ARRAY: return generateArrayValue();
            case JsonValueTypeEnum::TYPE_OBJECT: return generateObjValue();
        }
        throw std::runtime_error{ " no such json type" };
    }

    double JsonObjectFactory::generateNumValue() {
        assert(jsonStr_[pos_] == '-' || isDigit(jsonStr_[pos_]));
        size_t begin = pos_++;
        for (; isDigit(jsonStr_[pos_]); ++pos_);
        std::string_view sub = jsonStr_.substr(begin, pos_ - begin);
        return std::strtod(sub.data(), nullptr);
    }

    std::string JsonObjectFactory::generateStrValue() {
        assert(jsonStr_[pos_++] == '\"');
        size_t begin = pos_;
        for (; jsonStr_[pos_] != '\"'; ++pos_);
        return std::string { jsonStr_.substr(begin, pos_++ - begin) };
    }

    std::shared_ptr<JsonArray> JsonObjectFactory::generateArrayValue() {
        assert(jsonStr_[pos_] == '[');
        std::shared_ptr<JsonArray> jsonArray{ new JsonArray };
        while (jsonStr_[pos_++] != ']')  {
            skipWhiteSpace();
            JsonValueNode node = generateValueNode();
            jsonArray->values_.push_back(std::move(node));
            skipWhiteSpace();
            assert(jsonStr_[pos_] == ',' || jsonStr_[pos_] == ']');
        }
        return jsonArray;
    }

    bool JsonObjectFactory::generateBoolValue() {
        bool ret;
        if (jsonStr_[pos_] == 'f' && jsonStr_[++pos_] == 'a' && jsonStr_[++pos_] == 'l' && jsonStr_[++pos_] == 's' && jsonStr_[++pos_] == 'e') {
            ret = false;
        } else if (jsonStr_[pos_] == 't' && jsonStr_[++pos_] == 'r' && jsonStr_[++pos_] == 'u' && jsonStr_[++pos_] == 'e') {
            ret = true;
        } else {
            throw std::runtime_error{ "the value of bool is invalid" };
        }
        ++pos_;
        return ret;
    }

    decltype(nullptr) JsonObjectFactory::generateNullValue() {
        assert(jsonStr_[pos_++] == 'n' && jsonStr_[pos_++] == 'u' && jsonStr_[pos_++] == 'l' && jsonStr_[pos_++] == 'l');
        return nullptr;
    }


    JsonValueNode JsonObjectFactory::generateValueNode() {
        JsonValueTypeEnum type = getJsonTypeEnumByChar(jsonStr_[pos_]);
        JsonValueType value = generateValue(type);
        JsonValueNode node { type, value };
        return node;
    }

    std::shared_ptr<JsonObject> JsonObjectFactory::generateObjValue() {
        if (jsonStr_[pos_] != '{') throw std::runtime_error{ "json object string should start with '{'" };
        std::shared_ptr<JsonObject> object{ new JsonObject };
        while (jsonStr_[pos_++] != '}') {
            size_t begin = pos_;
            skipWhiteSpace();
            std::string_view key = generateKey();

            skipWhiteSpace();
            if (jsonStr_[pos_++] != ':') throw std::runtime_error{ "lack char ':' after key: " + std::string{ key } };

            skipWhiteSpace();
            JsonValueNode node = generateValueNode();
            object->nodes.emplace(key, std::move(node));

            skipWhiteSpace();
            size_t end = pos_;
            if (!(jsonStr_[pos_] == ',' || jsonStr_[pos_] == '}'))
                throw std::runtime_error{ "lack char '.' or '}' after " + std::string{ jsonStr_.substr(begin, end - begin + 1) } };
        }
        return object;
    }


}
